
#ifndef __RSP__FCBCONT__HEADER_FILE__
#define __RSP__FCBCONT__HEADER_FILE__

#include "rsp_def.h"

////////////////////////////////////////////////////////////////////////////////
///Circular container for fcbs on server
///The ObjInfo template argument should define the following static methods
/// uint ObjInfo::incremental( const uchar*& pc, uint& oid );
/// uint ObjInfo::full( ocnst char*& pc, uint& oid, token& clsid );
template <class ObjInfo>
struct fcb_container
{
    struct element;

    fcb_container()
    {
        fcbs.need( RSP_SETTINGS.nframes() );
        list_init();
    }


    ///Returns active fcb and prepares another leading fcb by flushing the oldest trailing fcb
    frame_command_buffer& active()
    {
        fcbs[ RSP_SETTINGS.active_slot(-RSP_SETTINGS._ntrailing) ].reset();
        return fcbs[ RSP_SETTINGS.active_slot() ];
    }

    frame_command_buffer& commited()                    { return fcbs[ RSP_SETTINGS.active_slot(-1) ]; }
    
    frame_command_buffer& leading(int fr)               { return fcbs[ RSP_SETTINGS.active_slot(fr) ]; }
    const frame_command_buffer& leading(int fr) const   { return fcbs[ RSP_SETTINGS.active_slot(fr) ]; }

    ///Called for each frame to update status of contained objects
    void frame()
    {
        frame_command_buffer& fcb = active();

        //execute object commands
        const uchar* pd = (const uchar*)fcb.ptr();
        const uchar* pe = pd + fcb.size();
        for( ; pd<pe; )
        {
            uint slot;
            uint len;
            if( !(len = ObjInfo::incremental( pd, slot )) )
                //this should happen on the client only
                //full state updates have to be applied after frame() methods
                break;

            element* bs = page.get_item(slot);

            bs->object->exec_commands( pd, pd+len );
            pd += len;
        }

        //process the living
        element** bsp = &living;
        while( bsp != last )
        {
            element* bs = *bsp;
            rsp_tile* obj = bs->object;
            rsp_tile* dst = obj->frame();

            //remove from the queue if object should die
            if( bs->should_die() )          del_contained_object(bsp);
            //moved away
            else if(dst)                    { dst->add_newcomer( del_contained_object(bsp) ); }
            //move to newcomers if a full update required
            else if( bs->should_renew() )   { add_newcomer( del_contained_object(bsp) ); }
            //proceed to next object
            else                            bsp = &bs->next;
        }

        last = bsp;

        //full state updates (client only)
        token clsid;
        for( ; pd<pe; )
        {
            uint len = ObjInfo::full( pd, slot, clsid );

            //fetch by oid if existing object, or create by class id
            element* bs = page.get_item(slot);
            bs->object = rsp_base::create_object(clsid);

            bs->state_load(pd);
            pd += len;
        }
    }


    ///Compute the incremental update to be sent to the bound connectors
    ///@note This is being run after the frame() method for each frame (server only)
    frame_command_buffer& compute_frame_updates()
    {
        //Here we compute the frame update for viewers bound to object on this tile
        //The update consists of FCB for the frame and a full update for newcomers.
        //This would be optimized later - if a viewer has also the tile from what the
        //newcomer came last frame, it doesn't have to be replicated

        frame_command_buffer& fcb = commited();

        //Append full updates of newcomers to the FCB that was commited last
        //Required only if there are some viewers bound
        if(*last)
        {
            element** next = last;
            while(*next)
            {
                element* bo = *next;

                uint offs = ObjInfo::pack_full( fcb, bo->object->slot );
                bo->object->state_save(fcb);
                *(ushort*)(fcb.ptr()+offs-sizeof(ushort)) = ushort(fcb.size() - offs);
                //(fcb.ptr()+offs-[ushort].size)[@ushort]@ = fcb.size() - offs;

                next = &bo->next;
            }

            last = next;
        }

        return fcb;
    }

    ///Create fcb with full updates of objects
    ///@note this is called after the compute_updates() method so the newcomers are in the living list already
    void create_full_update( frame_command_buffer& fcb ) const
    {
        element* bs = living;
        while(bs)
        {
            uint offs = ObjInfo::pack_full( fcb, bs->object->slot, bs->object->class_id() );
            bs->object->state_save(fcb);
            *(ushort*)(fcb.ptr()+offs-sizeof(ushort)) = ushort(fcb.size() - offs);

            bs = bs->next;
        }
    }

    ///Fill fcb with differential updates (fcbs) for every frame not acknowledged by client
    ///@param from negative number specifying relative frame number from active to go from
    void create_differential_update( frame_command_buffer& fcb, int from ) const
    {
        for( ; from < (int)RSP_SETTINGS._nleading; ++from )
            fcb.append( leading(from) );
    }

    ///Write command buffer for specified object and leading frame
    void write( rsp_base* obj, int leadfr, const void* data, uchar len )
    {
        frame_command_buffer& fcb = leading(leadfr);

        ::memcpy( ObjInfo::pack_incremental( fcb, obj->oid(), len ), data, len );
    }


public:
    ///Find first object of given class
    rsp_base* find_class( const token& clsname )
    {
        rsp_base** next = &living;
        while(*next) {
            rsp_base* bo = *next;
            if( clsname == bo->class_name() )
                return bo;
            next = &bo->next;
        }
        return 0;
    }

    ///Get object from required slot
    ///@note doesn't check i
    rsp_base* get_object_from_slot( uint slot ) const
    {
        element* el = page.get_item_safe(slot);
        if(!el) return 0;
        return el->object;
    }

    ///Add new object to the newcomer list
    ///@note the frame() function for them gets called starting from the next frame
    void ins_object( rsp_base* obj )
    {
        element* el = page.alloc();
        el->object = obj;
        el->next = *last;
        obj->slot = page.get_item_id(el);
        *last = el;
    }

    ///Remove given object from list
    ///@note costly but rare, traverses the list sequentially to find the one
    bool del_object( rsp_base* obj )
    {
        element* el = page.get_item(obj->slot);

        element** next = &living;
        while(*next) {              //or next!=last ?
            if( el == *next )
            {
                del_contained_object(next);
                return true;
            }
            next = &(*next)->next;
        }
        return false;
    }

    ///Delete all objects of specific class
    uint del_objects( const token& clsname )
    {
        element** next = &living;
        uint n=0;
        while( next != last ) {
            element* el = *next;
            if( el->object->class_name() == clsname ) {
                ++n;
                del_contained_object(next);
            }
            next = &(*next)->next;
        }
        return n;
    }

    ///Iterate over objects that have net connector bound to it
    ///@param obj object to iterate from, NULL for iterating from the beginning
    ///@return object with bound connector found, or NULL if none left
    rsp_base* next_connector( rsp_base* obj )
    {
        element* next = obj ? page.get_item(obj->slot) : living;
        while(next) {
            if( next->object->conn )  return next;
            next = next->next;
        }
        return next;
    }

private:
    rsp_base* del_contained_object( element** bsp )
    {
        element* bo = *bsp;
        *bsp = bo->next;
        if( &bo->next == last )
            last = bsp;
        rsp_base* p = bo->object;
        bo->object = 0;
        page.free(bo);
        return p;
    }

private:

    ///Initialize tile lists
    void list_init()
    {
        living = 0;
        last = &living;
    }

    ///Reset for the next frame (no newcomers)
    void list_reset()
    {
        //append newcome to living
        rsp_base** next = last;
        while(*next)  next = &(*next)->next;

        last = next;
    }

private:

    struct element
    {
        rsp_base* object;
        element* next;
    };

    chunkpage<element> page;
    
    element* living;                            ///< ptr to the first object in the stable list
    element** last;                             ///< ptr to the last non-new object's next pointer

    ///Array of frame command buffers for trailing and leading frames
    dynarray<frame_command_buffer> fcbs;
};

#endif //__RSP__FCBCONT__HEADER_FILE__
